/*
 * Created on 2010-8-16
 * Copyright 2009 by www.xfok.net. All Rights Reserved
 *
*/

package net.xfok.rss.parser;

import java.io.*;
import java.net.*;
import java.util.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

/**
 * @author YangHua
 *
 */
public class Rss {
	private static final int TYPE_UNKNOWN = 0;
    private static final int TYPE_RSS = 1;
    private static final int TYPE_ATOM = 2;
    
    private static final String NODE_RSS_ROOT = "rss";
    private static final String NODE_RSS_ITEM = "item";
    private static final String NODE_RSS_TITLE = "title";
    private static final String NODE_RSS_DESCRIPTION = "description";
    private static final String NODE_RSS_LINK = "link";
    private static final String NODE_RSS_PUBDATE = "pubDate";
    private static final String NODE_RSS_AUTHOR = "author";
    private static final String NODE_RSS_CREATOR = "dc:creator";
    
    private static final String NODE_ATOM_ROOT = "feed";
    private static final String NODE_ATOM_ITEM = "entry";
    private static final String NODE_ATOM_TITLE = "title";
    private static final String NODE_ATOM_SUBTITLE = "subtitle";
    private static final String NODE_ATOM_CONTENT = "content";
    private static final String NODE_ATOM_PUBLISHED = "published";
    private static final String NODE_ATOM_AUTHOR_NAME = "name";
    
    private static final Set<String> fetchChars = new HashSet<String>();

    static {
        fetchChars.add(NODE_RSS_TITLE);
        fetchChars.add(NODE_RSS_DESCRIPTION);
        fetchChars.add(NODE_RSS_LINK);
        fetchChars.add(NODE_RSS_AUTHOR);
        fetchChars.add(NODE_RSS_CREATOR);
        fetchChars.add(NODE_RSS_PUBDATE);

        fetchChars.add(NODE_ATOM_TITLE);
        fetchChars.add(NODE_ATOM_SUBTITLE);
        fetchChars.add(NODE_ATOM_CONTENT);
        fetchChars.add(NODE_ATOM_PUBLISHED);
        fetchChars.add(NODE_ATOM_AUTHOR_NAME);
    }
    
    private int type = TYPE_UNKNOWN;   
    private List<Feed> feedItems;
    private String rssTitle=null;
    private Feed currentItem = null;
    private boolean firstElement = true;
    private StringBuilder cache = new StringBuilder(4096);
    
    /**
     * constructor 
     * @param url rss address
     */
    public Rss(String url){
    	feedItems=this.getFeeds(url);
    }
    
    public String getRssTitle(){
    	return this.rssTitle;
    }
    
    public List<Feed> getFeeds() {
    	return this.feedItems;
    }
    
    private List<Feed> getFeeds(String url) {
    	InputStream is=post(url);
    	if(is!=null){
    		try{
    			feedItems=new ArrayList<Feed>();
    			FeedParserHandler handler=new FeedParserHandler();
    			SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
    			SAXParser parser = saxParserFactory.newSAXParser();
    			parser.parse(is, handler);
    		}catch(Exception e){
    			e.printStackTrace();
    		}
    	}
    	return feedItems;
    }
    
    private InputStream post(String url){
		InputStream is=null;
		try{	
			is=doPost(url);
		}catch(Exception e){
			e.printStackTrace();
		}
		return is;
	}
    
    private void checkItem(Feed item) throws SAXException {
        if (item.getLink()==null){
            throw new SAXException("Missing URL.");
        }
        if (item.getTitle()==null){
            item.setTitle("(Untitled)") ;
        }
        if (item.getAuthor()==null){
            item.setAuthor("(Unknown)") ;
        }
        if (item.getSummary()==null){
            item.setSummary("(No Summary)") ;
        }
    }
    
	private class FeedParserHandler extends DefaultHandler {
		public void characters(char[] ch, int start, int length)
				throws SAXException {
			if(cache!=null){
				 cache.append(ch, start, length);
				 if(rssTitle==null){
					 rssTitle=cache.toString();
					 cache=null;
				 }				
			}
		}
		
		public void startElement(String uri, String localName, String qName,
				Attributes attr) throws SAXException {
			if(firstElement){
				if(localName.equals(NODE_RSS_ROOT)){
					type=TYPE_RSS;
				}else if(localName.equals(NODE_ATOM_ROOT)){
					type=TYPE_ATOM;
				}	
				firstElement=false;
			}
			if(type==TYPE_RSS&&localName.equals(NODE_RSS_ITEM)||type==TYPE_ATOM&&localName.equals(NODE_ATOM_ITEM)){
				  currentItem = new Feed();
			}
			
			if (type==TYPE_ATOM && "link".equals(localName) && currentItem!=null) {
	            if ("alternate".equals(attr.getValue("rel"))) {
	                String url = attr.getValue("href");
	                if (url!=null){
	                    currentItem.setLink(url.trim());
	                }
	            }
	        }
			if (fetchChars.contains(localName)){
				cache = new StringBuilder(1024);
			}else{
				cache = null;
			}
		}
		
		public void endElement(String uri, String localName, String qName)
				throws SAXException {
			if ((type==TYPE_RSS && NODE_RSS_ITEM.equals(localName)) || (type==TYPE_ATOM && NODE_ATOM_ITEM.equals(localName))) {
	            checkItem(currentItem);
	            feedItems.add(currentItem);
	            currentItem = null;
	        }
            if (currentItem!=null&&cache!=null) {
                if (type==TYPE_RSS) {
                    if (NODE_RSS_TITLE.equals(localName)){
                        currentItem.setTitle(cache.toString().trim());
                    }
                    if (NODE_RSS_LINK.equals(localName)){
                        currentItem.setLink(cache.toString().trim());
                    }
                    if (NODE_RSS_AUTHOR.equals(localName) || NODE_RSS_CREATOR.equals(localName)){
                        currentItem.setAuthor(cache.toString().trim());
                    }
                    if (NODE_RSS_DESCRIPTION.equals(localName)){
                        currentItem.setSummary(cache.toString().trim());
                    }
                    if (NODE_RSS_PUBDATE.equals(localName)){
                        currentItem.setPubDate(cache.toString().trim());
                    }
                }
                if (type==TYPE_ATOM) {
                    if (NODE_ATOM_TITLE.equals(localName)){
                    	currentItem.setTitle(cache.toString().trim());
                    }
                    if (NODE_ATOM_AUTHOR_NAME.equals(localName)){
                    	currentItem.setAuthor(cache.toString());
                    }
                    if (NODE_ATOM_CONTENT.equals(localName)){
                    	currentItem.setSummary(cache.toString().trim());
                    }
                    if (NODE_ATOM_PUBLISHED.equals(localName)){
                    	currentItem.setPubDate(cache.toString().trim());
                    }
                }
            }
		}
	}
	
	/**
	 * get input stream from http connection
	 * 
	 * @return http inputstream
	 * @throws IOException 
	 */
	public InputStream doPost(String rssUrl){
		InputStream inputStream = null;
		/*
		 *  Open connection
		 */
		try{
			URL url = new URL(rssUrl.replace(" ", "%20"));
			HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
			try{
				int responseCode = urlConnection.getResponseCode();
				if (responseCode == HttpURLConnection.HTTP_OK) {
					inputStream = urlConnection.getInputStream();
				} else {
					inputStream = null;;
				}
			}catch(Exception e){
				e.printStackTrace();
			}	
		}catch(Exception e){
			e.printStackTrace();
		}
		return inputStream;
	}
}
